/*
Abstract:
  Source file parsing functionality implementation

Last changed:
  $Id: parser.cpp 67 2009-04-13 08:26:23Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/
#include "parser.h"
#include "../io/logging.h"
#include "../io/dataprovider.h"

#include <error.h>
#include <error_codes.h>
#include <symbol_check.h>

#include <limits>
#include <vector>
#include <cassert>
#include <sstream>
#include <algorithm>

#include <text.h>

#define FILE_TAG DA81A639

/*
  Syntax:

LINE ::= COMMAND|DEFINE|TEXT|EMPTY|(LINE|COMMENT)
COMMAND ::= +FILENAME
DEFINE ::= LOCAL|GLOBAL
LOCAL ::= '=' NAME
GLOBAL ::= '<' NAME
TEXT ::= KEY '>' VALUE| '>' VALUE
VALUE ::= OPERAND|TXT_VALUE|NAME|(VALUE|TXT_VALUE)|(VALUE|NAME)
OPERAND ::= +FILENAME+|`shellcmd`
NAME ::= [A-Za-z0-9_]+
KEY ::= [A-Za-z0-9_]+
TXT_VALUE ::= '"' TXT_SYMBOLS '"'
TXT_SYMBOLS ::= <empty>|<symbol> TXT_SYMBOLS|<symbol> ESC_SYMBOLS
ESC_SYMBOLS ::= \"|\\|\n|\t|\[0-9]+
*/

namespace
{
  typedef StrIterator<String> StringIterator;

  const String::value_type COMMENT_DELIMITER = '#';
  const String::value_type GLOBAL_DELIMITER = '<';
  const String::value_type LOCAL_DELIMITER = '=';
  const String::value_type INCLUDE_DELIMITER = '+';
  const String::value_type VALUE_DELIMITER = '>';
  const String::value_type QUOTE_DELIMITER = '\"';
  const String::value_type ESCAPE_DELIMITER = '\\';

  const Symbol::EscapePair PARSER_ESC[] = {{'\n', 'n'}, {'\t', 't'}, {'\\', '\\'}, {'\"', '\"'}, {'\r', 'r'}};

  enum Directive
  {
    UNKNOWN = 0,
    INCLUDE
  };

  //key => value
  typedef std::map<String, TokensList> MultitokenMap;

  struct MultiEntry
  {
    explicit MultiEntry(Entry::EntryType type = Entry::LOCAL) : Type(type)
    {
    }
    Entry::EntryType Type;
    MultitokenMap Value;
  };
  typedef std::map<String, MultiEntry> MultientryMap;

  class CommentFinder : public std::unary_function<String::value_type, bool>
  {
  public:
    CommentFinder() : InString(false), Prev()
    {
    }

    result_type operator()(argument_type arg)
    {
      if (COMMENT_DELIMITER == arg && !InString)
      {
        return true;
      }
      if (ESCAPE_DELIMITER == Prev)
      {
        if (arg == Prev) // escaped escape
        {
          arg = 0;
        }
      }
      else if (QUOTE_DELIMITER == arg)
      {
        InString = !InString;
      }
      Prev = arg;
      return false;
    }
  private:
    bool InString;
    String::value_type Prev;
  };

  String StripString(const String& str)
  {
    if (str.empty())
    {
      return str;
    }
    String::const_iterator end(std::find_if(str.begin(), str.end(), CommentFinder()));
    for (--end; Symbol::IsSpace(*end) && end != str.begin(); --end);
    ++end;
    String::const_iterator pos(str.begin());
    for (; pos != end && Symbol::IsSpace(*pos); ++pos);
    return String(pos, end);
  }

  Directive ParseDirective(const String& inStr, String& operand)
  {
    StringIterator it(inStr);
    if (!it || INCLUDE_DELIMITER != *it)
    {
      return UNKNOWN;
    }
    operand.clear();
    for (++it; it && Symbol::IsSpace(*it); ++it);
    if (!it)
    {
      return UNKNOWN;//maybe error?
    }
    while (it)
    {
      operand.push_back(*it);
      ++it;
    }
    return INCLUDE;
  }

  bool ParseType(const String& inStr, String& sectName, Entry::EntryType& type)
  {
    String section;
    StringIterator it(inStr);
    if (!it || (GLOBAL_DELIMITER != *it && LOCAL_DELIMITER != *it))
    {
      return false;
    }
    type = (GLOBAL_DELIMITER == *it) ? Entry::GLOBAL : Entry::LOCAL;
    for (++it; it && Symbol::IsSpace(*it); ++it);
    while (it && Symbol::IsLabel(*it))
    {
      section.push_back(*it);
      ++it;
    }
    if (!it)//finished
    {
      if (!section.empty())
      {
        sectName = section;
        return true;
      }
    }
    return false;
  }

  bool ExtractKeyName(StringIterator& it, String& keyName)
  {
    keyName.clear();
    while (it)
    {
      const String::value_type sym(*it);
      ++it;
      if (Symbol::IsSpace(sym))
      {
        if (!keyName.empty())
        {
          return false;//key with spaces- may be error
        }
      }
      else if (VALUE_DELIMITER == sym)
      {
        break;//parse text
      }
      else if (Symbol::IsKey(sym))
      {
        keyName.push_back(sym);
      }
      else
      {
        return false;//strange symbol- may be error
      }
    }
    return it;//error if invalid string
  }

  bool Unescape(StringIterator& it, String::value_type& val)
  {
    if (!++it)
    {
      return false;
    }
    if (Symbol::Unescape(*it, PARSER_ESC, val))
    {
      ++it;//next one
      return true;
    }
    if (!Symbol::IsDigit(*it))//invalid symbol
    {
      return false;
    }
    std::size_t numVal(0);
    while (it)
    {
      const String::value_type sym(*it);
      std::size_t newVal(numVal * 10 + sym - '0');
      //stop on alien symbol or overflow
      if (!Symbol::IsDigit(sym) ||
          newVal > static_cast<std::size_t>(std::numeric_limits<String::value_type>::max()))
      {
        val = static_cast<String::value_type>(numVal);
        return true;//stop conversion
      }
      numVal = newVal;
      ++it;
    }
    return false;
  }

  bool ExtractString(String::value_type delimiter, StringIterator& it, String& val)
  {
    //skip first symbol- delimiter
    ++it;
    while (it)
    {
      String::value_type sym(*it);
      if (ESCAPE_DELIMITER == sym)
      {
        if (!Unescape(it, sym))
        {
          return false;
        }
        val.push_back(sym);
        continue;//already incremented
      }
      else if (delimiter == sym)
      {
        break;//out of text loop
      }
      else //just text
      {
        val.push_back(sym);
      }
      ++it;
    }
    return it && (++it, true);//to skip finishing delimiter if success
  }

  void ExtractLabelValue(StringIterator& it, String& val)
  {
    while (it && Symbol::IsLabel(*it))
    {
      val.push_back(*it);
      ++it;
    }
  }

  bool ExtractValues(IO::SourceDataProvider& prov, StringIterator& it, TokensList& tokens)
  {
    while (it)
    {
      const String::value_type sym(*it);
      if (Symbol::IsSpace(sym))//skip spaces in common mode
      {
        ++it;
        continue;
      }
      if (QUOTE_DELIMITER == sym) //parse text
      {
        String val;
        if (!ExtractString(QUOTE_DELIMITER, it, val))
        {
          return false;
        }
        tokens.push_back(Token(Token::TEXT, val));
      }
      else if (INCLUDE_DELIMITER == sym)
      {
        String name;
        if (!ExtractString(INCLUDE_DELIMITER, it, name))
        {
          return false;
        }
        tokens.push_back(Token(Token::TEXT, name));
        prov.GetExternalFileContent(name, tokens.back().Value);
      }
      else if (Symbol::IsLabel(sym))//parse label
      {
        String val;
        ExtractLabelValue(it, val);
        tokens.push_back(Token(Token::VARIABLE, val));
      }
      else
      {
        return false;//may be error
      }
    }
    return true;
  }

  bool ParseValue(IO::SourceDataProvider& prov, const String& inStr, MultitokenMap& mtmap)
  {
    String keyName;
    StringIterator sit(inStr);
    return ExtractKeyName(sit, keyName) &&
           ExtractValues(prov, sit, mtmap[keyName]);
  }

  void ParseFile(IO::SourceDataProvider& prov, MultientryMap& subresult)
  {
    //context
    String curString;

    String sectName;
    Entry::EntryType etype(Entry::LOCAL);
    while (prov.GetLine(curString))
    {
      const String& workStr(StripString(curString));
      if (!workStr.empty())
      {
        String operand;
        switch (ParseDirective(workStr, operand))
        {
        case INCLUDE:
          prov.SetNestedSource(operand);
          continue;
        default:
          break;
        }
        if (ParseType(workStr, sectName, etype))
        {
          if (sectName.empty())
          {
            String curPos;
            prov.GetCurrentPosition(curPos);
            throw Error(ERROR_DETAIL, ERROR_INVALID_SECTION_NAME,
              StringFormatter::Process(TEXT_ERROR_INVALID_SECTION_NAME, curPos));
          }
          //try to insert
          if (!subresult.insert(MultientryMap::value_type(sectName, MultiEntry(etype))).second)
          {
            String curPos;
            prov.GetCurrentPosition(curPos);
            throw Error(ERROR_DETAIL, ERROR_DUPLICATE_SECTION,
              StringFormatter::Process(TEXT_ERROR_DUPLICATE_SECTION, sectName, curPos));
          }
        }
        else if (!ParseValue(prov, workStr, subresult[sectName].Value))
        {
          String curPos;
          prov.GetCurrentPosition(curPos);
          throw Error(ERROR_DETAIL, ERROR_SYNTAX_ERROR,
            StringFormatter::Process(TEXT_ERROR_SYNTAX_ERROR, curString, curPos));
        }
      }
    }
  }

  void Optimize(const MultientryMap& src, const StringArray& keys, EntriesMap& dst)
  {
    const String::value_type DEFAULT_KEY[] = {'\0'};

    //EntriesMap -> EntryValue
    dst.clear();
    for (MultientryMap::const_iterator it = src.begin(), lim = src.end(); it != lim; ++it)
    {
      Log::Debug("Selecting token '$1'", it->first);
      MultitokenMap::const_iterator byKey(it->second.Value.end());
      for (StringArray::const_iterator keyIt = keys.begin(), keyLim = keys.end(); keyIt != keyLim; ++keyIt)
      {
        byKey = it->second.Value.find(*keyIt);
        if (it->second.Value.end() != byKey)
        {
          Log::Debug(" used key '$1'", byKey->first);
          break;
        }
      }
      if (it->second.Value.end() == byKey)
      {
        byKey = it->second.Value.find(DEFAULT_KEY);
        if (it->second.Value.end() == byKey)
        {
          throw Error(ERROR_DETAIL, ERROR_KEY_NOT_FOUND,
            StringFormatter::Process(TEXT_ERROR_FIND_VALUE, it->first));
        }
      }
      TokensList& dstList(dst.insert(EntriesMap::value_type(it->first, Entry(it->second.Type))).first->second.Value);
      dstList.reserve(byKey->second.size());
      for (TokensList::const_iterator tit = byKey->second.begin(), tlim = byKey->second.end(); tit != tlim; ++tit)
      {
        //optimize text
        if (Token::TEXT == tit->Type &&
           !dstList.empty() &&
           Token::TEXT == dstList.back().Type)
        {
          dstList.back().Value += tit->Value;
        }
        else
        {
          dstList.push_back(*tit);
        }
      }
    }
  }

  void Escape(const String& txt, OutStream& str)
  {
    StringIterator it(txt);
    while (it)
    {
      const String::value_type sym(*it);
      String::value_type tmp;
      if (Symbol::Escape(sym, PARSER_ESC, tmp))
      {
        str << ESCAPE_DELIMITER << tmp;
      }
      else if (Symbol::IsEscaped(sym))
      {
        str << ESCAPE_DELIMITER << static_cast<std::size_t>(sym);
      }
      else
      {
        str << *it;
      }
      ++it;
    }
  }

  class EntryOut : public std::unary_function<EntriesMap::value_type, void>
  {
    const EntryOut& operator = (const EntryOut&);
  public:
    explicit EntryOut(OutStream& loc, OutStream& glb) : LocStream(loc), GlbStream(glb)
    {
    }

    result_type operator ()(const argument_type& arg) const
    {
      assert(Entry::GLOBAL == arg.second.Type || Entry::LOCAL == arg.second.Type);
      bool isGlobal(Entry::GLOBAL == arg.second.Type);
      OutStream& stream(isGlobal ? GlbStream : LocStream);
      stream << '\n' << (isGlobal ? GLOBAL_DELIMITER : LOCAL_DELIMITER) << ' ' << arg.first << '\n' <<
      VALUE_DELIMITER << ' ';
      if (arg.second.Value.empty())
      {
        stream << QUOTE_DELIMITER << QUOTE_DELIMITER;
      }
      for (TokensList::const_iterator it = arg.second.Value.begin(), lim = arg.second.Value.end(); it != lim; ++it)
      {
        if (Token::TEXT == it->Type)
        {
          stream << QUOTE_DELIMITER;
          Escape(it->Value, stream);
          stream << QUOTE_DELIMITER;
        }
        else if (Token::VARIABLE == it->Type)
        {
          stream << it->Value;
        }
        else
        {
          assert(!"Invalid entry type while output");
        }
        stream << ' ';
      }
      stream << '\n';
    }
  private:
    OutStream& LocStream;
    OutStream& GlbStream;
  };
}

namespace Parser
{
  void Parse(IO::SourceDataProvider& prov, const Parameters& params, EntriesMap& result)
  {
    MultientryMap subresult;
    //initial parse
    ParseFile(prov, subresult);
    //process
    Optimize(subresult, params.Keys, result);
  }

  void Unparse(const EntriesMap& data, const Parameters& /*params*/, OutStream& str)
  {
    OStringStream glob;
    str << COMMENT_DELIMITER << ' ' << TEXT_GENERATED_FILE << std::endl <<
           COMMENT_DELIMITER << ' ' << TEXT_LOCAL_ENTRIES;
    std::for_each(data.begin(), data.end(), EntryOut(str, glob));
    str << COMMENT_DELIMITER << ' ' << TEXT_GLOBAL_ENTRIES << glob.str();
  }
}
